<?php

/* ========================================================================= */
/* This file is part of the HypnoPHP Framework Library                       */
/* Copyright (c) 2009-2010, Radu Buzea                                       */
/* ========================================================================= */

/**
* @author Radu Buzea
* @copyright Copyright (c) 2009-2010, Radu Buzea (www.hypnosis.ro)
* @license GNU Lesser General Public License (www.gnu.org/copyleft/lesser.html)
* @package HypnoPHP
* @category Core
* @version 1.0.3
*/

# ###########################################################################

define('HYPNOPHP_APPLICATION', true);
define('HYPNOPHP_PHP_MIN_VER', '5.1.4');
define('HYPNOPHP_VERSION',     '1.0.3');

// Check the PHP version. HypnoPHP requires the min version given by HYPNOPHP_PHP_MIN_VER
if (version_compare(phpversion(), HYPNOPHP_PHP_MIN_VER, '<') === true) {
	$message_title = "Oops, HypnoPHP startup error !";
	$message_text = "Looks like you have an older version of PHP.<br />" .
		"You are using PHP " . phpversion() . " but HypnoPHP requires PHP " . HYPNOPHP_PHP_MIN_VER .
		" or newer.<br /><br /><a href=\"http://www.php.net/downloads.php\">Go here</a> and " .
		"download the latest version of PHP.";
	exit ("<div align=\"center\" style=\"padding:20px;font-family:Verdana,Arial,sans-serif\">" .
		"<h2>{$message_title}</h2><p>{$message_text}</p></center>");
}

# ###########################################################################

// Include common application files (globals, constants, internal methods...)

include "HFactory.php";
include "HErrorHandler.php";

include "H_Globals.php";
include "H_Constants.php";

// ============================================================================
// Final class: HApplication
// ============================================================================

final class HApplication extends HBaseObject
{

	# ###########################################################################

	/**
	* Run the application
	*
	* @param string $app_path The application directory
	* @return void
	* @access public
	* @static
	*/
	public static function run($app_path = '', $app_environment = 'default')
	{
		global $_HAPPLICATION_ROOT_PATH, $_HAPPLICATION_PATHS;
		$app = self::getInstance();
		$app->exec_time = microtime(true);
		$app->path = '';
		$app->rewrite_mode = true;
		$app->request_uri = '';
		$app->config = new HCollection();
		$app->request_controller = null;
		$app->environment = $app_environment;
		$app->database = null;
		$app->ob_count = 0;
		$app->attached_objects = null;

		// Check the application path
		$app_path = trim($app_path);
		if ($app_path == '') {
			if (defined('HAPPLICATION_PATH') && is_dir(HAPPLICATION_PATH)) {
				$app_path = HAPPLICATION_PATH;
			} else {
				$app_path = realpath(dirname(HSYSTEM_PATH)) . DS . 'application';
			}
		}
		if ($app_path != '' && is_dir($app_path)) {
			// Set the global application path (in various modes)
			$_HAPPLICATION_ROOT_PATH = realpath(trim($app_path) . DS);
			$app->path = $_HAPPLICATION_ROOT_PATH;
			if (!defined('HAPPLICATION_PATH')) { define('HAPPLICATION_PATH', $app_path); }
		} else {
			// The application path is invalid or was not specified
			if ($app_path != '') { $err_msg = "Invalid application path <i>{$app_path}</i>"; }
			else { $err_msg = "The application path was not specified"; }
			trigger_error($err_msg, E_USER_ERROR);
			die(PHP_EOL . "<!-- EDIT YOUR 'index.php' FILE AND SET THE CORRECT APPLICATION PATH -->" . PHP_EOL);
		}
		// Prepare a list of folder paths (system and application files)
		$_HAPPLICATION_PATHS = array(
			'app_cache'       => sprintf('%s' . DS . 'cache', $app_path),
			'app_config'      => sprintf('%s' . DS . 'config', $app_path),
			'app_controllers' => sprintf('%s' . DS . 'controllers', $app_path),
			'app_errors'      => sprintf('%s' . DS . 'errors', $app_path),
			'app_helpers'     => sprintf('%s' . DS . 'helpers', $app_path),
			'app_models'      => sprintf('%s' . DS . 'models', $app_path),
			'app_modules'     => sprintf('%s' . DS . 'modules', $app_path),
			'app_views'       => sprintf('%s' . DS . 'views', $app_path),
			'app_assets'      => sprintf('%s' . DS . 'assets', $app_path),
			'app_services'    => sprintf('%s' . DS . 'services', $app_path),
			'system_errors'   => HSYSTEM_PATH . DS . 'errors',
			'system_helpers'  => HSYSTEM_PATH . DS . 'helpers',
			'system_lib'      => HSYSTEM_LIB_PATH,
			'system_modules'  => HSYSTEM_PATH . DS . 'modules',
		);
		// Load configuration (INI file or PHP script file)
		$ini_loaded = self::loadConfigINI($app_path . DS . 'config' . DS . 'application.ini');
		if (!$ini_loaded) {
			$config_file = $app_path . DS . 'config' . DS . 'config.php';
			if (is_file($config_file)) { include $config_file; }
		}
		// Include helper files
		if (is_dir($_HAPPLICATION_PATHS['system_helpers'])) {
			$helpers_dir = $_HAPPLICATION_PATHS['system_helpers'];
			if ($helpers_dir_handle = opendir($helpers_dir)) {
				while (false !== ($helpers_file = readdir($helpers_dir_handle))) {
					$helpers_file = $helpers_dir . DS . $helpers_file;
					if (is_file($helpers_file) && pathinfo($helpers_file, PATHINFO_EXTENSION) == 'php') {
						include $helpers_file;
					}
				}
				closedir($helpers_dir_handle);
			}
		}
		$rewrite_mode = self::getConfig('rewrite_mode');
		$app->rewrite_mode = ($rewrite_mode != null && $rewrite_mode);
		$print_exec_time = self::getConfig('print_exec_time');
		if ($print_exec_time == null && defined('HGLOBAL_PRINT_EXEC_TIME')) {
			$print_exec_time = HGLOBAL_PRINT_EXEC_TIME;
		}
		$app->print_exec_time = ($print_exec_time != null && $print_exec_time);
		$debug_mode = HApplication::getConfig('debug_mode');
		$app->debug_mode = (is_bool($debug_mode) && $debug_mode);
		// Initialize basic componets
		HRequest::initRequest();
		HSession::initsSession();
		HCookie::initCookie();
		HRouter::initRouter();
		HResponse::init_response();
		$app->request_uri = HRequest::requestURI();
		// Include routes file
		$routes_file = $app_path . DS . 'config' . DS . 'routes.php';
		if (is_file($routes_file)) { include $routes_file; }
		// Find the request controller and run it
		$output = HApplication::runController();
		// Send controller output as response
		HResponse::output($output);
		if (!HResponse::is_text_output()) { exit; }
		// Application global benchmark stop timer
		if ($app->print_exec_time && HResponse::get_content_type() == 'text/html') {
			if (defined('HGLOBAL_FMT_EXEC_TIME')) {
				printf(HGLOBAL_FMT_EXEC_TIME, self::getAppExecTimer());
			} else {
				printf("\n<!-- Execution time: %.4f seconds -->\n", self::getAppExecTimer());
			}
		}
	}

	# ###########################################################################

	/**
	* Get application execution time
	*
	* @param integer (unisgned) The optional number of returned decimal digits (default: 8)
	* @return float Timestamp with microseconds
	* @access public
	* @static
	*/
	public static function getAppExecTimer($precision = 4)
	{
		$app = self::getInstance();
		if ($precision < 0) { $precision = 0; }
		$app_exec_time = (microtime(true) - $app->exec_time);
		return round($app_exec_time, $precision);
	}

	# ###########################################################################

	/**
	* Search the controller class and the action inside the controller then
	* execute the action
	*
	* @param string $ctrl_name The controller name
	* @param string $action_name The action name - must be a public function inside the controller class
	* @param array $action_params An array with action params (optional)
	* @return string The output buffer
	* @access public
	* @static
	*/
	public static function runController($ctrl_name = '', $action_name = 'index', $action_params = array())
	{
		$app = self::getInstance();
		$request_controller = $app->request_controller;
		$content_type = 'text/html';
		$is_special_route = false;
		if ($ctrl_name == '') {
			// Find the route (controller/action) for the requested address
			$rt = HRouter::findRoute($app->request_uri);
			if (is_array($rt) && isset($rt['controller']) && $rt['controller'] != '') {
				$ctrl_name = $rt['controller'];
				$is_special_route = (isset($rt['special_route']) && $rt['special_route']);
				if (isset($rt['action']) && $rt['action'] != '') {
					$action_name = $rt['action'];
				}
				if (isset($rt['params']) && is_array($rt['params'])) {
					$action_params = array_merge($action_params, $rt['params']);
				}
				if (isset($rt['content-type'])) {
					$content_type = trim(strtolower($content_type));
				}
			}
		}
		// Init the controller object given by the $ctrl_name
		if (is_null($request_controller)) {
			$ctrl_object = null;
			if ($ctrl_name != '') {
				$ctrl_class = HFactory::loader($ctrl_name);
				if ($ctrl_class) {
					$ctrl_object = HFactory::factory($ctrl_name);
				}
			}
		} else {
			$ctrl_object = $request_controller;
		}
		if (is_object($ctrl_object)) {
			$ctrl_object = HFactory::factory($ctrl_name);
			if ($is_special_route && method_exists($ctrl_object, $action_name)) {
				// Execute the special action
				return $ctrl_object->$action_name($action_name);
			} else if (method_exists($ctrl_object, 'run')) {
				// Check if the object has the 'run' method and execute it
				if (is_null($request_controller)) {
					$app->request_controller = $request_controller = $ctrl_object;
				}
				$ctrl_object->run();
				// Execute the controller action given by $action_name
				return $ctrl_object->executeAction($action_name, $action_params, $content_type);
			}
			// Error: The object is not a valid controller
			$error_text = "Cannot find the <i>run</i> method inside the <i>{$ctrl_name}</i> class";
			trigger_error($error_text, E_USER_ERROR);
			exit;
		} else if ($ctrl_name != '') {
			$error_text = "Unable to load the class <i>{$ctrl_name}</i>";
		} else {
			// If the $controller is empty then send 'error 404: file not found'
			$request_uri = $app->requestURI();
			if ($request_uri == '') { $request_uri = '/'; }
			$error_text = "Cannot find the controller for the requested address: <i>{$request_uri}</i>";
		}
		return self::displayErrorPage(404, $error_text);
	}

	# ###########################################################################

	/**
	* Display an error page
	*
	* @param integer $error_code The error code (example 404)
	* @param string $debug_error_text The error text (example 'Page not found')
	* @return string
	* @access public
	* @static
	*/
	public static function displayErrorPage($error_code = 0, $debug_error_text = '')
	{
		if ($error_code == 0) { return ''; }

		// Load the HErrorPage controller
		$error_controller_exists = class_exists('HErrorPage', false);
		if (!$error_controller_exists) {
			if (HFactory::loader('HErrorPage')) {
				$error_controller_exists = class_exists('HErrorPage', false);
			}
		}
		if ($error_controller_exists) {
			$app = self::getInstance();
			$app->request_controller = $request_controller = new HErrorPage();
			$request_controller->run();
			$error_page_return = $request_controller->executeAction("errorPage", $error_code);
			if ($error_page_return != '') { return $error_page_return; }
		}

		// Error:: The HErrorPage controller cannot be found or has an empty return
		// Trigger debug errror (if any)...
		if ($debug_error_text != '') { $debug_error_text = "{$error_code} Error: " . $debug_error_text; }
		else { $debug_error_text = "{$error_code} Error"; }
		trigger_error($debug_error_text, E_USER_WARNING);
		return '';
	}

	# ###########################################################################

	/**
	* Get the application path
	*
	* @return string
	* @access public
	* @static
	*/
	public static function applicationPath()
	{
		$app = self::getInstance();
		return $app->path;
	}

	# ###########################################################################

	/**
	* Get if the application will use rewrite mode
	*
	* @return bool
	* @access public
	* @static
	*/
	public static function rewriteMode()
	{
		$app = self::getInstance();
		return $app->rewrite_mode;
	}

	# ###########################################################################

	/**
	* Get if the requested URI
	*
	* @return string
	* @access public
	* @static
	*/
	public static function requestURI()
	{
		$app = self::getInstance();
		return $app->request_uri;
	}

	# ###########################################################################

	/**
	* Get if the application is in debug mode
	*
	* @return bool
	* @access public
	* @static
	*/
	public static function debugMode()
	{
		$app = self::getInstance();
		return $app->debug_mode;
	}

	# ###########################################################################

	/**
	* Set if the application prints the execution time
	*
	* @param bool $value
	* @return void
	* @access public
	* @static
	*/
	public static function setPrintExecTime($value = true)
	{
		$app = self::getInstance();
		$app->print_exec_time = boolval($value);
	}

	# ###########################################################################

	/**
	* Get application path or array of paths
	*
	* @param string $path
	* @return string or FALSE on error
	* @access public
	* @static
	*/
	public static function getPath($path = '')
	{
		global $_HAPPLICATION_PATHS;
		$path = trim(strtolower($path));
		if ($path == '') { return $_HAPPLICATION_PATHS; }
		if (isset($_HAPPLICATION_PATHS[$path])) { return $_HAPPLICATION_PATHS[$path]; }
		return false;
	}

	# ###########################################################################

	/**
	* Get the application environment name
	*
	* @return string
	* @access public
	* @static
	*/
	public static function getEnvironment()
	{
		$app = self::getInstance();
		$app_environment = trim($app->environment);
		return $app_environment;
	}

	# ###########################################################################

	/**
	* Get configuration params from the application's global configuration settings
	*
	* @param string $param
	* @return mixed
	* @access public
	* @static
	*/
	public static function getConfig($param = '')
	{
		$app = self::getInstance();
		$config_data = $app->config;
		if (!isset($config_data) || !is_object($config_data)) {
			$app->config = new HCollection();
			return null;
		}
		$param = trim($param);
		if ($app->__checkVarName($param)) {
			return $config_data->$param;
		}
		return null;
	}

	# ###########################################################################

	/**
	* Add a configuration parameter into the application's global configuration settings
	*
	* @param string $param The configuration parameter name
	* @param mixed $value The configutation parameter value
	* @return void
	* @access public
	* @static
	*/
	public static function setConfig($param, $value = null)
	{
		$app = self::getInstance();
		$config_data = $app->config;
		if (!isset($config_data) || !is_object($config_data)) {
			$config_data = new HCollection();
		}
		if (is_array($param) && count($param) > 0) {
			$no_checks = (is_bool($value) && $value);
			foreach ($param as $var_name => $var_value) {
				if (!$no_checks && !$app->__checkVarName($var_name)) { continue; }
				$config_data->$var_name = $var_value;
			}
		} else if (is_string($param) && !is_null($value)) {
			$param = trim($param);
			if ($app->__checkVarName($param)) {
				$config_data->$param = $value;
			}
		}
		$app->config = $config_data;
	}

	# ###########################################################################

	/**
	* Check if a configuration parameter exists
	*
	* @param string $param
	* @return bool
	* @access public
	* @static
	*/
	public static function configExists($param = '')
	{
		$app = self::getInstance();
		$config_data = $app->config;
		if (!isset($config_data) || !is_object($config_data)) {
			$app->config = new HCollection();
			return false;
		}
		$param = trim($param);
		if ($app->__checkVarName($param)) {
			return isset($config_data->$param);
		}
		return false;
	}

	# ###########################################################################

	/**
	* Load configuration parameters from an application INI file
	*
	* @param string $config_ini_file The configuration INI file path
	* @return bool
	* @access public
	* @static
	*/
	public static function loadConfigINI($config_ini_file = '')
	{
		if (!is_file($config_ini_file)) { return false; }
		$app_environment = self::getEnvironment();
		if ($app_environment == '') { $app_environment = 'default'; }
		$config_data = parse_ini_file($config_ini_file, true);
		$db_connections = array();
		$config_found = 0;
		foreach ($config_data as $config_section => $config_params) {
			$penv = strpos($config_section, $app_environment . ".");
			if ($penv !== false && $penv == 0) {
				if ($config_section ==  $app_environment . ".database") {
					$db_connections[] = $config_params;
				} else {
					foreach ($config_params as $param => $value) {
						self::setConfig($param, $value);
					}
				}
				$config_found++;
			}
		}
		if (count($db_connections) > 0) {
			self::setConfig('db_connections', $db_connections);
		}
		return ($config_found > 0);
	}

	# ###########################################################################

	/**
	* Increment the counter for the output buffer control calls
	*
	* @return integer
	* @access public
	* @static
	*/
	public static function incObCount()
	{
		$app = self::getInstance();
		$ob_count = $app->ob_count + 1;
		$app->ob_count = $ob_count;
		return $ob_count;
	}

	# ###########################################################################

	/**
	* Decrement the counter for the output buffer control calls
	*
	* @return integer
	* @access public
	* @static
	*/
	public static function decObCount()
	{
		$app = self::getInstance();
		$ob_count = $app->ob_count - 1;
		if ($ob_count < 0) { $ob_count = 0; }
		$app->ob_count = $ob_count;
		return $ob_count;
	}

	# ###########################################################################

	/**
	* Get the counter for the output buffer control calls
	*
	* @return integer
	* @access public
	* @static
	*/
	public static function getObCount()
	{
		$app = self::getInstance();
		return $app->ob_count;
	}

	# ###########################################################################

	/**
	* Get the application database (using just one global database object)
	*
	* @return HBaseDb object
	* @access public
	* @static
	*/
	public static function getDatabase()
	{
		$app = self::getInstance();
		$database = $app->database;
		if (is_null($database) || !is_object($database)) {
			$dbsys = '';
			// Get the function first argument (has to be ineger)
			$db_index = (func_num_args() > 0) ? intval(func_get_arg(0)) : 0;
			// Get database connection setings from the application config
			$dbcon = self::getConfig('db_connections');
			if (is_array($dbcon) && count($dbcon) > 0) {
				if (isset($dbcon[$db_index])) {
					$dbsys = isset($dbcon[$db_index]['db_systme']) ? $dbcon[$db_index]['db_systme'] : '';
				}
			}
			if ($dbsys != '') { $dbsys = trim(strtolower($dbsys)); }
			// Initialize the database object for given by $dbsys (database system/engine)
			switch ($dbsys) {
				case 'mysql':
					$app->database = $database = new Module_MySQL_Service($db_index);
					break;
				case 'mysqli':
					/** @todo: NOT IMPLEMENTED (raise error) */
					$app->database = $database = new Module_MySQLi_Service($db_index);
					break;
				case 'postgresql':
				case 'postgres':
					 /** @todo: NOT IMPLEMENTED (raise error) */
					$app->database = $database = new Module_PostgreSQL_Service($db_index);
					break;
				case 'sqlite':
					 /** @todo: NOT IMPLEMENTED (raise error) */
					$app->database = $database = new Module_SQLite_Service($db_index);
					break;
				default:
					$app->database = null;
			}
		}
		return $database;
	}

	# ###########################################################################

	/**
	* Get a list of available modules
	*
	* @return array
	* @access public
	* @static
	*/
	public static function getModules()
	{
		$app = self::getInstance();
		$modules = $app->modules;
		if (isset($modules) && is_array($modules)) { return $modules; }
		$modules = array();
		global $_HAPPLICATION_PATHS;
		$module_paths = array(
			sprintf($_HAPPLICATION_PATHS['app_modules'], HApplication::applicationPath()),
			HSYSTEM_MODULES_PATH,
		);
		foreach($module_paths as $modules_path) {
			if (is_dir($modules_path)) {
				$modules_dir = dir($modules_path);
				while (($module_name = $modules_dir->read()) !== false) {
					if ($module_name != '.' && $module_name != '..') {
						if (is_dir(realpath($modules_path) . DS . $module_name)) {
							if (!isset($modules[$module_name])) {
								$modules[$module_name] = realpath($modules_path) . DS . $module_name;
							}
						}
					}
				}
				$modules_dir->close();
			}
		}
		$app->modules = $modules;
		return $modules;
	}

	# ###########################################################################

	/**
	* Find the directory path for a module
	*
	* @param string $module_name The module name
	* @param string $module_file The module file (optional)
	* @return string The module file path
	* @access public
	* @static
	*/
	public static function findModulePath($module_name = '', $module_file = '')
	{
		$modules = self::getModules();
		$path = null;
		if (is_array($modules) && isset($modules[$module_name])) {
			$path = $modules[$module_name];
		}
		if ($path != null && $module_file != '') {
			$module_file = str_replace(array('Module_', '_'), array('', DS), $module_file) . '.php';
			if (is_file($path . DS . $module_file)) { return $path . DS . $module_file; }
		}
		return $path;
	}

	# ###########################################################################

	/**
	* Get or set include paths
	*
	* @param array $paths
	* @return array
	* @access public
	* @static
	*/
	public static function includePaths($paths = null)
	{
		if (is_array($paths) && count($paths) > 0) {
			$paths = implode(PATH_SEPARATOR, $paths);
		}
		if (is_string($paths) && strlen($paths)) {
			set_include_path($paths . PATH_SEPARATOR . get_include_path());
		}
		return get_include_path();
	}

	# ###########################################################################

	/**
	* Get the PHP version
	*
	* @param array $paths
	* @return array
	* @access public
	* @static
	*/
	public static function phpVersion(string $what_part)
	{
		$raw_version = preg_split("/[.]/", phpversion());
		switch ($what_part) {
			case 'major':   return $raw_version[0];
			case 'minor':   return $raw_version[1];
			case 'release': return $raw_version[2];
			default: return array(
				'major'   => $raw_version[0],
				'minor'   => $raw_version[1],
				'release' => $raw_version[2],
			);
		}
	}

	# ###########################################################################

	/**
	* Attach an object to the application
	*
	* @param string $object_name
	* @param object $object
	* @param bool $overwrite
	* @return bool
	* @access public
	* @static
	*/
	public static function attachObject($object_name = '', $object = null, $overwrite = false)
	{
		$object_name = trim($object_name);
		$app = self::getInstance();
		if (!$app->__checkVarName($object_name) || !is_object($object)) { return false; }
		$objects = $app->attached_objects;
		if (!is_array($objects)) { $objects = array(); }
		if (isset($objects[$object_name]) && is_object($objects[$object_name]) && !$overwrite) {
			$error_text = "An object named <em>{$object_name}</em> is already attached to the application.";
			trigger_error($error_text, E_USER_ERROR);
		}
		$objects[$object_name] = $object;
		$app->attached_objects = $objects;
		return true;
	}

	# ###########################################################################

	/**
	* Get ab attached object
	*
	* @param string $object_name
	* @return object
	* @access public
	* @static
	*/
	public static function getAttachedObject($object_name = '')
	{
		$object_name = trim($object_name);
		$app = self::getInstance();
		$objects = $app->attached_objects;
		if ($app->__checkVarName($object_name) && is_array($objects)) {
			if (is_object($objects[$object_name])) { return $objects[$object_name]; }
		}
		return null;
	}

	# ###########################################################################

	/**
	* Get the application instance (singleton)
	*
	* @return HApplication
	* @access public
	* @static
	*/
	public static function getInstance()
	{
		return self::singleton(__CLASS__);
	}

	# ###########################################################################

}
